////////////////////////////////////////////////////////////////////////////////////////
//
// JSimpleSim - A machine language simulator based on SimpSim by Anne-Gert Bultena.
//
// Copyright (C) 2008 Gerardo Camorlinga, Ryan Fitzgerald, and RJ Gordon
//
// This file is part of JSimpleSim.
//
// JSimpleSim is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// JSimpleSim is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with JSimpleSim.  If not, see <http://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////////////////////

package jsimplesim.model;

import java.util.Arrays;

import javax.swing.table.*;
import org.apache.log4j.Logger;

import jsimplesim.view.Disassemble;
import jsimplesim.view.JSimpleSimView;

/**
 * A custom table model for our hex table component. Facilitates the storage of
 * hex digits into bytes, so that they can be easily serialized. Also handles
 * the storage and retrieval of our data.
 * 
 * @author f08maroon
 */
@SuppressWarnings("unused")
public class MemHexTableModel extends AbstractTableModel {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1783486695185751638L;
	static Logger logger = Logger.getLogger(MemHexTableModel.class);
	private static final String HEX = "0123456789ABCDEF";
	private static final int CHARACTERS_PER_ROW = 16;
	private static String[] columnNames = { "Address", "0", "1", "2", "3", "4",
			"5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
	private boolean isAddress = true;
	private byte[] buffer;
	private boolean[][] newItem;
	private int index;
	/**
     *
     */
	public JSimpleSimView view;

	/**
     *
     */
	public MemHexTableModel() {
		buffer = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0 };
		this.resetBuffer(buffer);
		setNewItem(new boolean[16][17]);
	}

	/**
	 * Default Constructor. Initializes the model to all zero's just like the
	 * SimpleSim version.
	 * 
	 * @param view1
	 */
	public MemHexTableModel(JSimpleSimView view1) {
		this.view = view1;
		buffer = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0 };
		this.resetBuffer(buffer);
		setNewItem(new boolean[16][17]);
	}

	/**
	 * Sets the data model to a new model. Used when loading binary files, or
	 * clearing the contents of the view.
	 * 
	 * @param buffer
	 *            An array of bytes with the data we want. Has to be 256 in
	 *            length.
	 * 
	 */
	public void resetBuffer(byte[] buffer) {
		this.buffer = buffer;
		fireTableDataChanged();

	}

	/**
	 * Getter method for our data.
	 * 
	 * @return An array of bytes containing our data.
	 */
	public byte[] getBuffer() {
		return this.buffer;
	}

	/**
	 * Getter method for the number of columns in our model.
	 * 
	 * @return The number of columns.
	 */
	public int getColumnCount() {
		return columnNames.length;
	}

	@Override
	public String toString() {
		return "MemHexTableModel [isAddress=" + isAddress + ", buffer="
				+ Arrays.toString(buffer) + ", newItem="
				+ Arrays.toString(newItem) + ", view=" + view + "]";
	}

	/**
	 * Getter method for the number of rows in our model.
	 * 
	 * @return The number of rows.
	 */
	public int getRowCount() {
		if (buffer == null) {
			return 0;
		} else {
			return (int) Math.ceil(buffer.length / (float) CHARACTERS_PER_ROW);
		}
	}

	/**
	 * Method for calculating the index given row and column
	 * 
	 * @return integer value for the index
	 */
	public int calcIndex(int row, int col) {
		int index;
		return (index = (row * CHARACTERS_PER_ROW) + col - 1);
	}

	/**
	 * Getter method for the current index
	 * 
	 * @return The index of the cell being edited
	 */
	public int getIndex() {
		return index;
	}

	/**
	 * Getter method for a specific column of our model.
	 * 
	 * @param col
	 *            The index of the column in the model.
	 * @return The name of the column.
	 */
	@Override
	public String getColumnName(int col) {
		return columnNames[col];
	}

	/**
	 * Getter method for a data element in the model.
	 * 
	 * @param row
	 *            The row index of the element.
	 * @param col
	 *            The column index of the element.
	 * @return The element in the form of an Object. It's actually a String.
	 */
	public Object getValueAt(int row, int col) {
		// Address Column

		if (col == 0) {
			if (isAddress) {
				return this.byteToHex(row * CHARACTERS_PER_ROW) + " ";
			} else {
				return (row * CHARACTERS_PER_ROW) + " "; // Text Column
			}
		}
		int index = (row * CHARACTERS_PER_ROW) + col - 1;

		if (index < buffer.length) {
			return byteToHex(buffer[index]);
		} else {
			return null;
		}
	}

	/**
	 * Getter method that gets the type of a column.
	 * 
	 * @param c
	 *            Not used for anything
	 * @return All columns are of type String so returns String.class.
	 */
	@Override
	public Class<String> getColumnClass(int c) {
		return String.class;
	}

	/**
	 * Checks to see whether a cell is editable or not. Depending on whether the
	 * cell is under the address column or not.
	 * 
	 * @param row
	 *            The row index.
	 * @param col
	 *            The column index.
	 * @return Whether or not cell is editable.
	 */
	@Override
	public boolean isCellEditable(int row, int col) {
		if (col == 0) {
			return false;
		}
		int index = (row * CHARACTERS_PER_ROW) + col - 1;

		if (index < buffer.length) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Updates the model with changes to the view.
	 * 
	 * @param value
	 *            The updated value of the view.
	 * 
	 * @param row
	 *            The index of the row.
	 * @param col
	 *            The index of the column.
	 */
	@Override
	public void setValueAt(Object value, int row, int col) {
		String hexString = value.toString();
		logger.debug("hexString = " + hexString);

		String oldValue = (String) this.getValueAt(row, col);

		if (!oldValue.equals(hexString)) {
			clearOldItems();
			getNewItem()[row][col] = true;

			logger.debug("Old value: " + oldValue
					+ ", is not equal to new value: " + hexString);
			if (hexString.length() > 0) {
				byte hexByte = (byte) Integer.parseInt(hexString, 16);
				logger.debug("hexByte = " + hexByte);
				index = calcIndex(row, col);

				logger.debug("index = " + index);
				logger.debug("buffer length = " + buffer.length);
				logger.debug("column count = " + getColumnCount());
				buffer[index] = hexByte;
				logger.info("buffer[index] : " + buffer[index]);
				fireTableCellUpdated(row, col);

				/*
				 * Team Alpha edit: no idea why the disassembler should run
				 * every time a memory cell updates, so we are removing this to
				 * make debug logs clearer previous code preserved within this
				 * comment: Disassemble.run(this, this.view);
				 */
				// this.view.changeTextColor(row, col);
			}
		}
	}

	private void clearOldItems() {
		for (int i = 0; i < 16; i++) {
			for (int j = 0; j < 17; j++) {
				if (getNewItem()[i][j]) {
					getNewItem()[i][j] = false;
				}
			}
		}
	}

	/**
	 * Changes a byte into a string.
	 * 
	 * @param value
	 *            The byte to convert.
	 * @return The string representation of the byte.
	 */
	private String byteToHex(int value) {
		int hi = (value & 0xF0) >> 4;
		int lo = (value & 0x0F);
		return "" + HEX.charAt(hi) + HEX.charAt(lo);
	}

	/**
	 * Changes an int into a string.
	 * 
	 * @param value
	 *            The int to convert.
	 * @return The string representatin of the int.
	 */
	private String intToHex(int value) {
		int hi = (value & 0xFF00) >> 8;
		int lo = (value & 0x00FF);
		return byteToHex(hi) + byteToHex(lo);
	}

	/**
	 * 
	 * @param newItem
	 */
	public void setNewItem(boolean[][] newItem) {
		this.newItem = newItem;
	}

	/**
	 * 
	 * @return
	 */
	public boolean[][] getNewItem() {
		return newItem;
	}
}
